<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>

</body>
<script>
    //【1】在上面的分析中，我们知道，我们知道，当传入的 value 是一个函数时， value 还要经过一个叫 optimizeCb 的内置函数才能获得最终的iteratee：
    var cb = function(value, context, argCount) {
        // ...
        if (_.isFunction(value)) return optimizeCb(value, context, argCount);
        // ...
    };

    // 顾名思义， optimizeCb 有优化回调的意思，所以他是一个对最终返回的 iteratee 进行优化的过程，我们看到他的源码：
    /** 优化回调(特指函数中传入的回调)
     *
     * @param func 待优化回调函数
     * @param context 执行上下文
     * @param argCount 参数个数
     * @returns {function}
     */
    var optimizeCb = function(func, context, argCount) {
        // 一定要保证回调的执行上下文存在【如果不存在，直接返回待优化回调函数】
        if (context === void 0) return func;
        //参数个数为null直接指定为3，如果不是null则取传入数值
        switch (argCount == null ? 3 : argCount) {
            case 1:
                return function(value) {
                    return func.call(context, value);
                };
            case 2:
                return function(value, other) {
                    return func.call(context, value, other);
                };
            case 3:
                return function(value, index, collection) {
                    return func.call(context, value, index, collection);
                };
            case 4:
                return function(accumulator, value, index, collection) {
                    return func.call(context, accumulator, value, index, collection);
                };
        }
        return function() {
            return func.apply(context, arguments);
        };
    };
    // optimizeCb 的总体思路就是：传入待优化的回调函数 func ，以及迭代回调需要的参数个数 argCount ，根据参数个数分情况进行优化。
    //【1-1】argCount == 1 ，即 iteratee 只需要 1 个

    // 在 underscore 的 _.times 函数的实现中， _.times 的作用是执行一个传入的iteratee 函数 n 次，
    // 并返回由每次执行结果组成的数组。它的迭代过程iteratee 只需要 1 个参数 -- 当前迭代的索引：

    // 执行 iteratee 函数 n 次，返回每次执行结果构成的数组
    _.times = function(n, iteratee, context) {
        var accum = Array(Math.max(0, n));
        iteratee = optimizeCb(iteratee, context, 1);
        for (var i = 0; i < n; i++) {
            accum[i] = iteratee(i);
        }
        return accum;
    };
    // 看一个 _.times 的使用例子:
    function getIndex(index) {
        return index;
    }
    var results = _.times(3, getIndex); // => [0,1,2]
    //【1-2】argCount == 2， 即 iteratee 需要 2 个参数
    // 该情况在 underscore 没用使用， 所以最新的 master 分支已经不再考虑这个参数个数为 2 的情况。

    //【1-3】argCount == 3（ 默认）， 即 iteratee 需要 3 个参数
    // 这 3 个参数是：
    // value： 当前迭代元素的值
    // index： 迭代索引
    // collection： 被迭代集合
    // 在 _.map, _.each, _.filter 等函数中， 都是给 argCount 赋值了 3：
    _.each([1, 2, 3], function(value, index, collection) {
        console.log("被迭代的集合：" + collection + "；迭代索引：" + index + "；当前迭代的元素值：" + value);
    });
    // 被迭代的集合：1,2,3；迭代索引：0；当前迭代的元素值：1
    // 被迭代的集合：1,2,3；迭代索引：1；当前迭代的元素值：2
    // 被迭代的集合：1,2,3；迭代索引：2；当前迭代的元素值：3



    //【1-4】argCount == 4 ，即 iteratee 需要 4 个参数
    // 这 4 个参数分别是:
    // accumulator ：累加器
    // value ：迭代元素
    // index ：迭代索引
    // collection ：当前迭代集合

    // 那么这个累加器是什么意思呢？在 underscore 中的内部函数 createReducer中，就涉及到了 4 个参数的情况。
    // 该函数用来生成 reduce 函数的工厂，underscore 中的 _.reduce 及 _.reduceRight 都是由它创建的：

    /**
     * reduce 函数的工厂函数, 用于生成一个 reducer, 通过参数决定 reduce 的方向
     * @param dir 方向 left or right
     * @returns {function}
     */
    var createReduce = function(dir) {
        // dir此处传入的值是1
        var reducer = function(obj, iteratee, memo, initial) {
            // obj:数据源[1, 2, 3, 4, 5]
            // iteratee：迭代器【就是下面的那个_.reduce的第二个参数，那个函数function】
            // memo：为初始遍历索引0
            //initial：布尔值
            debugger
            var keys = !isArrayLike(obj) && _.keys(obj), //false
                length = (keys || obj).length, //5
                index = dir > 0 ? 0 : length - 1; //0
            // memo 用来记录最新的 reduce 结果
            // 如果 reduce 没有初始化 memo, 则默认为首个元素 (从左开始则为第一个元素, 从右则为最后一个元素)
            if (!initial) { //此处为false
                memo = obj[keys ? keys[index] : index];
                index += dir;
            }
            for (; index >= 0 && index < length; index += dir) {
                var currentKey = keys ? keys[index] : index; //第一次遍历为index值，是0
                // 执行 reduce 回调, 刷新当前值
                //每一次遍历，memo是累加到的值，obj[currentKey]为当前元素值，currentKey为当前索引，obj为数据源[1, 2, 3, 4, 5]
                memo = iteratee(memo, obj[currentKey], currentKey, obj);
            }
            return memo;
        };
        //下面代码只执行一次，上面的reducer会调用多次。【此处的本质是暴露出一个reducer函数调用，
        // 而reducer内部有遍历，该遍历在不断调用传入的函数（也就是_.reduce()的第二个参数】
        return function(obj, iteratee, memo, context) {
            //obj为初始数据源，
            // iteratee：迭代器【就是下面的那个_.reduce的第二个参数，那个函数function】
            // memo：为累加初始数值26
            // context此处调用为undefined
            // 如果参数正常, 则代表已经初始化了 memo

            var initial = arguments.length >= 3; //布尔值【此处为实参长度】

            // reducer 因为引入了累加器, 所以优化函数的第三个参数传入了 4,
            // 这样, 新的迭代回调第一个参数就是当前的累加结果
            return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial);
        };
    };
    // 我们可以看到， createReduce 最终创建的 reducer 就是需要一个累加器，该累加器需要被初始化，看一个利用 _.reduce 函数求和的例子：
    var sum = _.reduce([1, 2, 3, 4, 5], function(accumulator, value, index, collection) {
        // accumulator每次迭代的累加值，
        // value：当前遍历到的元素
        // index：当前遍历到的元素的索引
        // collection:数据源[1, 2, 3, 4, 5]

        //accumulator累加值，value当前遍历到的元素值
        return accumulator + value;
    }, 26); // => 15;
</script>

</html>